#include "Modifier/SpawnSetsInstruction.h"

#include "Modifier/GameModifier.h"

#include "Levels/CatSpawnSet.h"
#include "Supporting/Enums/EnumCatType.h"

#include "Director/DirectorLoader.h"
#include "SpawnDifficultyLoader.h"
#include "SpawnSets.h"

using namespace cocos2d;

//===== CONSTRUCTOR =====
SpawnSets::SpawnSets(CCArray* pSpawnSets)
{
	_numOfCatMultiplier = 1.0;
	_intervalMultiplier = 1.0;
	_numOfCats = 0;

	_pCatTypes = 0;
	_pCatDifficulty = 0;
	_pCatCounter = 0;
	_pCatIntervals = 0;

	initCatTypes(pSpawnSets);
	setEquationVariable();
}

//===== DESTRUCTOR =====
SpawnSets::~SpawnSets(void)
{
	delete[] _pCatTypes;
	delete[] _pCatDifficulty;
	delete[] _pCatCounter;
	delete[] _pCatIntervals;
}

//===== PUBLIC FUNCTIONS =====

/*!
    @function update
    @param		difficulty: int
				new difficulty level to determine factor contribution
				propertyDifficulty: int
				difficulty level for the spawn property
    @result		update the current difficulty level
    			sends a Modifier Instruction if new difficulty is set
*/
void SpawnSets::update(int difficulty, int propertyDifficulty)
{
	if( difficulty <= 0 )
	{
		return;
	}

	updateMultiplier(propertyDifficulty);
	getSpawnCount( difficulty );

	// Send spawn set instruction to Modifier
	for( int i=0; i<_numOfCats; i++ )
	{
		int typeOfCat = _pCatTypes[i];
		float spawnInterval = _pCatIntervals[i] * _intervalMultiplier;
		int numberToSpawn = _pCatCounter[i] * _numOfCatMultiplier;
		int prespawnDelay = 0;

		if( i%2 == 0)
		{
			prespawnDelay = 1;
		}

		if( numberToSpawn > 0 )
		{
			//CCLog("Instruction (Spawn Set): interval %f | spawning %i | type %i.", spawnInterval, numberToSpawn, typeOfCat);
			GameModifier::getInstance().modify( new SpawnSetsInstruction( new CatSpawnSet(typeOfCat, spawnInterval, numberToSpawn, prespawnDelay) ));
		}
	}
}

//===== PRIVATE FUNCTIONS =====

/*!
    @function updateMultiplier
    @param		propertyDifficulty: int
				difficulty level for the spawn property
    @result		update the multiplier values
*/
void SpawnSets::updateMultiplier(int propertyDifficulty)
{
	if( propertyDifficulty <= 0 )
	{
		return;
	}

	_numOfCatMultiplier = getNumOfCatsMultiplier(propertyDifficulty);
	_intervalMultiplier = getIntervalMultiplier(propertyDifficulty);
}

/*!
    @function getSpawnCount
    @param		difficulty: int
				difficulty level to count the max spawns
    @result		update _pCatCounter which counts the number of each type of cat to spawn
*/
void SpawnSets::getSpawnCount( int difficulty )
{
	if( _numOfCats <= 0 )
	{
		return;
	}

	// All cat array values have been initialized
	// Count number of cats to spawn
	bool canAddSet = true;
	int randomLimit = _numOfCats;

	// Reset spawn count
	for( int i=0; i<_numOfCats; i++ )
	{
		_pCatCounter[i] = 0;
	}

	int tempDifficulty = 1;

	// Send Instructions for new difficulty
	// Send a spawn cat instruction as long as calculated difficulty does not exceed current
	while(canAddSet)
	{
		int randomNum = arc4random() % randomLimit;
		int catDifficulty = _pCatDifficulty[randomNum];

		tempDifficulty += catDifficulty;

		if(tempDifficulty <= difficulty)
		{
			_pCatCounter[randomNum]++;
		}
		else
		{
			// If the difficulty level is too high,
			// lower the random limit and try again
			randomLimit--;

			if(tempDifficulty == difficulty)
			{
				canAddSet = false;
			}
			else
			{
				tempDifficulty -= catDifficulty;

				if(randomLimit <= 0)
				{
					canAddSet = false;
				}
			}
		}
	}

}

/*!
    @function initCatTypes
    @param		pSpawnSets: CCArray
    @result		Get the number of unique cat types from pSpawnSets.
    			Update _pCatDifficulty and interval with its corresponding value
*/
void SpawnSets::initCatTypes(CCArray* pSpawnSets)
{
	if( pSpawnSets->count() == 0 )
	{
		return;
	}

	int pCatTypeCounter[NUM_OF_TYPES] = {0};
	int pCatTypeIndex[NUM_OF_TYPES] = {0};

	// For each CatSpawnSet, count the number of each type
	for(int i=0; i < pSpawnSets->count(); i++)
	{
		CatSpawnSet* pSpawnSet = (CatSpawnSet*)(pSpawnSets->objectAtIndex(i));

		switch( pSpawnSet->getTypeOfCat() )
		{
			case BOUNCE_CAT:
				pCatTypeCounter[0]++;
				pCatTypeIndex[0] = BOUNCE_CAT;
				break;

			case FAT_CAT:
				pCatTypeCounter[1]++;
				pCatTypeIndex[1] = FAT_CAT;
				break;

			case NINJA_CAT:
				pCatTypeCounter[2]++;
				pCatTypeIndex[2] = NINJA_CAT;
				break;

			case OLYMPIC_CAT:
				pCatTypeCounter[3]++;
				pCatTypeIndex[3] = OLYMPIC_CAT;
				break;

			case PUNK_CAT:
				pCatTypeCounter[4]++;
				pCatTypeIndex[4] = PUNK_CAT;
				break;

			case WITCH_CAT:
				pCatTypeCounter[5]++;
				pCatTypeIndex[5] = WITCH_CAT;
				break;

			default:
				break;
		}
	}

	// Count number of each type of cats in pSpawnSets
	for(int i=0; i<NUM_OF_TYPES; i++)
	{
		if(pCatTypeCounter[i] > 0)
		{
			_numOfCats++;
		}
	}

	// Initialise the types
	_pCatTypes = new int[_numOfCats];
	_pCatDifficulty = new int[_numOfCats];
	_pCatCounter = new int[_numOfCats];
	_pCatIntervals = new float[_numOfCats];

	int index = 0;

	for(int i=0; i<NUM_OF_TYPES; i++)
	{
		if(pCatTypeCounter[i] > 0)
		{
			_pCatTypes[index] = pCatTypeIndex[i];
			_pCatDifficulty[index] = SpawnDifficultyLoader::getCatDifficulty( pCatTypeIndex[i] );
			_pCatIntervals[index] = SpawnDifficultyLoader::getCatInterval( pCatTypeIndex[i] );
			index++;
		}
	}
}

/*!
    @function setEquationVariable
    @result		set variable K and C to be used in the equation, y = kx + c
 */
void SpawnSets::setEquationVariable()
{
	float POINT_Y1, POINT_Y2, POINT_X1, POINT_X2;

	POINT_Y1 = DirectorLoader::NUM_CAT_MULTIPLIER_Y1;
	POINT_Y2 = DirectorLoader::NUM_CAT_MULTIPLIER_Y2;

	POINT_X1 = DirectorLoader::NUM_CAT_MULTIPLIER_X1;
	POINT_X2 = DirectorLoader::NUM_CAT_MULTIPLIER_X2;

	// y = kx + c
	_varCatK = DirectorLoader::getPosLineVarK(POINT_Y1, POINT_Y2, POINT_X1, POINT_X2);
	_varCatC = DirectorLoader::getPosLineVarC(_varCatK, POINT_Y1, POINT_X1);

	POINT_Y1 = DirectorLoader::INTERVAL_MULTIPLIER_Y1;
	POINT_Y2 = DirectorLoader::INTERVAL_MULTIPLIER_Y2;

	POINT_X1 = DirectorLoader::INTERVAL_MULTIPLIER_X1;
	POINT_X2 = DirectorLoader::INTERVAL_MULTIPLIER_X2;

	// y = -kx + c
	_varIntervalK = DirectorLoader::getNegLineVarK(POINT_Y1, POINT_Y2, POINT_X1, POINT_X2);
	_varIntervalC = DirectorLoader::getNegLineVarC(_varIntervalK, POINT_Y1, POINT_X1);
}

/*!
    @function getNumOfCatsMultiplier
    @result		find cat multiplier based on y = kx + c
    			x = ( y-c ) / k
*/
float SpawnSets::getNumOfCatsMultiplier(int propertyDifficulty)
{
	return DirectorLoader::getPosLineValue(_varCatK, _varCatC, propertyDifficulty);
}

/*!
    @function getIntervalMultiplier
    @result		find cat multiplier based on y = -kx + c
    			x = ( c-y ) / k
*/
float SpawnSets::getIntervalMultiplier(int propertyDifficulty)
{
	return DirectorLoader::getNegLineValue(_varIntervalK, _varIntervalC, propertyDifficulty);
}


